﻿using System;
using System.Collections.Generic;
using System.Reflection;
using MugenInjection.Core;
using MugenInjection.Interface;
using MugenInjection.Scope;

namespace MugenInjection.Infrastructure
{
    /// <summary>
    ///     Represents the managed scope.
    /// </summary>
    internal class ManagedScope : DisposableObject, IManagedScope, IEquatable<ManagedScope>
    {
        #region Equality members

        /// <summary>
        ///     Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <returns>
        ///     true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.
        /// </returns>
        /// <param name="other">
        ///     An object to compare with this object.
        /// </param>
        public bool Equals(ManagedScope other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return _id.Equals(other._id);
        }

        /// <summary>
        ///     Determines whether the specified <see cref="T:System.Object" /> is equal to the current <see cref="T:System.Object" />.
        /// </summary>
        /// <returns>
        ///     true if the specified <see cref="T:System.Object" /> is equal to the current <see cref="T:System.Object" />; otherwise, false.
        /// </returns>
        /// <param name="obj">
        ///     The <see cref="T:System.Object" /> to compare with the current <see cref="T:System.Object" />.
        /// </param>
        /// <exception cref="T:System.NullReferenceException">
        ///     The <paramref name="obj" /> parameter is null.
        /// </exception>
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != GetType()) return false;
            return Equals((ManagedScope)obj);
        }

        /// <summary>
        ///     Serves as a hash function for a particular type.
        /// </summary>
        /// <returns>
        ///     A hash code for the current <see cref="T:System.Object" />.
        /// </returns>
        public override int GetHashCode()
        {
            return _id.GetHashCode();
        }

        public static bool operator ==(ManagedScope left, ManagedScope right)
        {
            return Equals(left, right);
        }

        public static bool operator !=(ManagedScope left, ManagedScope right)
        {
            return !Equals(left, right);
        }

        #endregion

        #region Fields

        private readonly Guid _id;
        private readonly IInjector _injector;

        #endregion

        #region Constructor

        /// <summary>
        ///     Initializes a new instance of the <see cref="ManagedScope" /> class.
        /// </summary>
        public ManagedScope(IInjector injector)
        {
            Validate.ArgumentNotNull(injector, "injector");
            if (injector.IsDisposed)
                throw new ObjectDisposedException("The injector is disposed.");
            _injector = injector;
            injector.Disposed += InjectorOnDisposed;
            _id = Guid.NewGuid();
        }

        #endregion

        #region Methods

        private void InjectorOnDisposed(IDisposableObject disposableObject)
        {
            _injector.Disposed -= InjectorOnDisposed;
            Dispose();
        }

        #endregion

        #region Implementation of IServiceProvider

        /// <summary>
        ///     Gets the service object of the specified type.
        /// </summary>
        /// <returns>
        ///     A service object of type <paramref name="serviceType" />.
        ///     -or-
        ///     null if there is no service object of type <paramref name="serviceType" />.
        /// </returns>
        /// <param name="serviceType">
        ///     An object that specifies the type of service object to get.
        /// </param>
        object IServiceProvider.GetService(Type serviceType)
        {
            lock (ManagedScopeLifecycle.ScopeLocker)
            {
                var oldScope = ManagedScopeLifecycle.GetManagedScope(false);
                ManagedScopeLifecycle.SetManagedScope(this);
                object obj = _injector.GetService(serviceType);
                ManagedScopeLifecycle.SetManagedScope(oldScope);
                return obj;
            }
        }

        #endregion

        #region Implementation of IInjectorResolver

        /// <summary>
        ///     Creates the <see cref="IBindingContext" /> for the current <see cref="IInjectorResolver" />.
        /// </summary>
        /// <param name="service">
        ///     The specifed service <see cref="Type" />.
        /// </param>
        /// <param name="member">
        ///     The specified <see cref="MemberInfo" /> when injected specified service. Can be null.
        /// </param>
        /// <param name="parameterInfo">
        ///     The specified <see cref="ParameterInfo" /> when injected specified service.
        /// </param>
        /// <param name="parameters">The specified parameters for service.</param>
        /// <param name="specialParameters">The special parameters for resolve specified service.</param>
        /// <returns>
        ///     An instance of <see cref="IBindingContext" />.
        /// </returns>
        public IBindingContext CreateContext(Type service, MemberInfo member, ParameterInfo parameterInfo,
                                             IEnumerable<IInjectionParameter> parameters,
                                             IDictionary<string, object> specialParameters)
        {
            return _injector.CreateContext(service, member, parameterInfo, parameters, specialParameters);
        }

        /// <summary>
        ///     Injects values into properties, fields, methods in the specified object.
        /// </summary>
        /// <param name="target">The specified target for inject.</param>
        /// <param name="bindingContext">
        ///     The specified <see cref="IBindingContext" />.
        /// </param>
        public void Inject<T>(ref T target, IBindingContext bindingContext)
        {
            lock (ManagedScopeLifecycle.ScopeLocker)
            {
                var oldScope = ManagedScopeLifecycle.GetManagedScope(false);
                ManagedScopeLifecycle.SetManagedScope(this);
                _injector.Inject(ref target, bindingContext);
                ManagedScopeLifecycle.SetManagedScope(oldScope);
            }
        }


        /// <summary>
        /// Determines whether the specified request can be resolved.
        /// </summary>
        /// <param name="bindingContext"> The specified <see cref="IBindingContext"/>.</param>
        /// <param name="searchInParent">If set to <c>true</c> search in parent bindings.</param>
        /// <param name="includeImplicitBindings">If set to <c>false</c> implicit bindings are ignored.</param>
        /// <returns><c>True</c> if the specified service has been resolved; otherwise, <c>false</c>.</returns>
        public bool CanResolve(IBindingContext bindingContext, bool searchInParent, bool includeImplicitBindings)
        {
            lock (ManagedScopeLifecycle.ScopeLocker)
            {
                var oldScope = ManagedScopeLifecycle.GetManagedScope(false);
                ManagedScopeLifecycle.SetManagedScope(this);
                bool canResolve = _injector.CanResolve(bindingContext, searchInParent, includeImplicitBindings);
                ManagedScopeLifecycle.SetManagedScope(oldScope);
                return canResolve;
            }
        }

        /// <summary>
        ///     Resolves an instance for the specified <see cref="IBindingContext" />.
        /// </summary>
        /// <param name="bindingContext">
        ///     The specified <see cref="IBindingContext" />.
        /// </param>
        /// <returns>An instance of the service.</returns>
        public object Resolve(IBindingContext bindingContext)
        {
            lock (ManagedScopeLifecycle.ScopeLocker)
            {
                var oldScope = ManagedScopeLifecycle.GetManagedScope(false);
                ManagedScopeLifecycle.SetManagedScope(this);
                object obj = _injector.Resolve(bindingContext);
                ManagedScopeLifecycle.SetManagedScope(oldScope);
                return obj;
            }
        }

        /// <summary>
        /// Resolves all instances for the specified <see cref="IBindingContext"/>.
        /// </summary>
        /// <param name="bindingContext">The specified <see cref="IBindingContext"/>.</param>
        /// <param name="useParentToResolve">If <c>true</c> also use parent bindings to resolve.</param>
        /// <returns>A series of instances of the service.</returns>
        public IList<object> ResolveAll(IBindingContext bindingContext, bool useParentToResolve)
        {
            lock (ManagedScopeLifecycle.ScopeLocker)
            {
                var oldScope = ManagedScopeLifecycle.GetManagedScope(false);
                ManagedScopeLifecycle.SetManagedScope(this);
                IList<object> obj = _injector.ResolveAll(bindingContext, useParentToResolve);
                ManagedScopeLifecycle.SetManagedScope(oldScope);
                return obj;
            }
        }

        /// <summary>
        /// Resolves all instances for the specified service <see cref="Type"/>.
        /// </summary>
        /// <param name="service">Service for resolve.</param>
        /// <param name="parameters">Parameters for service.</param>
        /// <param name="specialParameters">Special parameters for resolve specified service.</param>
        /// <param name="useParentToResolve">If <c>true</c> also use parent bindings to resolve.</param>
        /// <returns>A series of instances of the service.</returns>
        public IList<object> ResolveAll(Type service, IEnumerable<IInjectionParameter> parameters, IDictionary<string, object> specialParameters, bool useParentToResolve)
        {
            lock (ManagedScopeLifecycle.ScopeLocker)
            {
                var oldScope = ManagedScopeLifecycle.GetManagedScope(false);
                ManagedScopeLifecycle.SetManagedScope(this);
                IList<object> obj = _injector.ResolveAll(service, parameters, specialParameters, useParentToResolve);
                ManagedScopeLifecycle.SetManagedScope(oldScope);
                return obj;
            }
        }

        #endregion

        #region Implementation of IManagedScope

        /// <summary>
        /// Gets the source <see cref="IInjector"/>.
        /// </summary>
        public IInjector Source
        {
            get { return _injector; }
        }

        #endregion
    }
}